package pl.edu.pw.ee.buildcity.scheduler;

import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Logger;
import org.quartz.*;
import pl.edu.pw.ee.buildcity.entities.BuildDefinition;
import pl.edu.pw.ee.buildcity.scheduler.api.BuildCityScheduler;
import pl.edu.pw.ee.buildcity.services.BuildDefinitionService;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;

import static pl.edu.pw.ee.buildcity.scheduler.BuildCityJobDetail.DEFAULT_JOBS_GROUP;
import static pl.edu.pw.ee.buildcity.scheduler.BuildCityJobDetail.TRIGGERS_PREFIX;

/**
 * @author mgrzechocinski
 * @since 2009-10-18
 */
public class BuildCitySchedulerImpl implements BuildCityScheduler {

	private static final Logger log = Logger.getLogger(BuildCitySchedulerImpl.class);

	private final Scheduler scheduler;

	@Resource(name = "buildDefinitionService")
	private BuildDefinitionService buildDefinitionService;

	// Constructor args injected by Spring

	public BuildCitySchedulerImpl(Scheduler quartzScheduler, JobListener listener) throws SchedulerException {
		this.scheduler = quartzScheduler;
		registerListener(listener);
		scheduler.start();
	}

	@PostConstruct
	private void loadJobs() {
		List<BuildDefinition> storedBuildDefinitions = buildDefinitionService.getAll(BuildDefinition.class);
		for (BuildDefinition buildDef : storedBuildDefinitions) {
			loadJob(buildDef);
		}
	}

	private void loadJob(BuildDefinition buildDef) {
		String cronExpr = buildDef.getCronExpr();
		String buildDefName = buildDef.getName();
		BuildCityJobDetail jobDetail = new BuildCityJobDetail(buildDefName).withCronTrigger(cronExpr);
		save(jobDetail);
		log.info("Loaded job definition: " + buildDefName + " with cron: " + cronExpr);
	}

	private void registerListener(JobListener listener) {
		try {
			log.debug("Registering job execution listener...");
			this.scheduler.addGlobalJobListener(listener);
			log.info("Job execution listener successfully registered!");
		} catch (SchedulerException e) {
			String msg = "Could not set job executions listener";
			throw SchedulerRuntimeException.withMessageAndException(msg, e);
		}
	}

	@Override
	public void save(BuildCityJobDetail jobDetail) throws SchedulerRuntimeException {
		try {
			if (jobExists(jobDetail)) {
				log.debug("Trying to update job " + jobDetail);
				updateExistingJob(jobDetail);
				log.info("Job " + jobDetail + " successfully updated.");
			} else {
				log.debug("Trying to add job " + jobDetail);
				scheduler.addJob(jobDetail, true);
				if (jobDetail.hasTrigger()) {
					scheduler.scheduleJob(jobDetail.getCronTrigger());
				}
				log.info("Job " + jobDetail + " successfully added to scheduler.");
			}
		} catch (SchedulerException e) {
			throw SchedulerRuntimeException.withException(e);
		}
	}

	private boolean jobExists(BuildCityJobDetail jobDetail) {
		try {
			BuildCityJobDetail existingJob = getJobDetail(jobDetail.getName());
			return existingJob != null;
		} catch (SchedulerRuntimeException e) {
			return false;
		}
	}

	private void updateExistingJob(BuildCityJobDetail jobDetail) throws SchedulerRuntimeException {
		try {
			String jobName = jobDetail.getName();

			// If user wants to delete trigger, adds empty cron expr.
			if (!jobDetail.hasTrigger()) {
				//durability of job has to set to true (job will be removed from scheduler in other case)
				scheduler.unscheduleJob(TRIGGERS_PREFIX + jobName, DEFAULT_JOBS_GROUP);
				return;
			}
			Trigger[] triggers = scheduler.getTriggersOfJob(jobName, DEFAULT_JOBS_GROUP);

			//Unschedule previous triggers, if there were any...
			if (!ArrayUtils.isEmpty(triggers)) {
				for (Trigger trigger : triggers) {
					log.debug("Unscheduling previous job " + jobName + " trigger: " + trigger.getName());
					scheduler.unscheduleJob(trigger.getName(), trigger.getGroup());
				}
			}
			log.info("Scheduling new trigger to " + jobDetail.getName() + ": " + jobDetail.getCronExpr());
			scheduler.scheduleJob(jobDetail.getCronTrigger());
			log.debug("Cron trigger " + jobDetail.getCronExpr() + " added for job: " + jobName + ". Next fire time is " + jobDetail.getNexFireTime());

		} catch (SchedulerException e) {
			throw SchedulerRuntimeException.withException(e);
		}
	}

	@Override
	public BuildCityJobDetail getJobDetail(String jobName) {
		try {
			JobDetail foundJob = scheduler.getJobDetail(jobName, DEFAULT_JOBS_GROUP);
			if (foundJob == null || !(foundJob instanceof BuildCityJobDetail)) {
				throw SchedulerRuntimeException.withMessage("BuildCityJobDetail '" + jobName + "' not found");
			}
			return (BuildCityJobDetail) foundJob;
		} catch (SchedulerException e) {
			throw SchedulerRuntimeException.withException(e);
		}
	}

	@Override
	public void fireNow(String jobName) {
		try {
			log.info("Trying to manually fire job " + jobName);
			scheduler.triggerJob(jobName, DEFAULT_JOBS_GROUP);
		} catch (SchedulerException e) {
			throw SchedulerRuntimeException.withException(e);
		}
	}

	//For unit tests
	Scheduler getScheduler() {
		return scheduler;
	}
}
